package com.xuan.system.service.question.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.xuan.common.core.constants.Constants;
import com.xuan.common.core.domain.TableDataInfo;
import com.xuan.common.core.enums.ResultCode;
import com.xuan.common.core.exception.BusinessException;
import com.xuan.system.domain.contest.entity.Contest;
import com.xuan.system.domain.contestQuestion.entity.ContestQuestion;
import com.xuan.system.domain.question.dto.QuestionAddDTO;
import com.xuan.system.domain.question.dto.QuestionEditDTO;
import com.xuan.system.domain.question.dto.QuestionQueryDTO;
import com.xuan.system.domain.question.entity.Question;
import com.xuan.system.domain.question.es.QuestionES;
import com.xuan.system.domain.question.vo.QuestionDetailVo;
import com.xuan.system.domain.question.vo.QuestionVo;
import com.xuan.system.elasticsearch.QuestionRepository;
import com.xuan.system.manager.QuestionCacheManager;
import com.xuan.system.mapper.ContestMapper;
import com.xuan.system.mapper.ContestQuestionMapper;
import com.xuan.system.mapper.QuestionMapper;
import com.xuan.system.service.question.QuestionService;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

/**
 * @author ASUS
 * @description 针对表【question(题目表)】的数据库操作Service实现
 * @createDate 2024-08-14 16:00:37
 */
@Service
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question>
        implements QuestionService {

    @Resource
    private QuestionMapper questionMapper;
    @Resource
    private ContestQuestionMapper contestQuestionMapper;
    @Resource
    private ContestMapper contestMapper;
    @Resource
    private QuestionRepository questionRepository;
    @Resource
    private QuestionCacheManager questionCacheManager;


    @Override
    public TableDataInfo listQuestion(QuestionQueryDTO questionQueryDTO) {
        Integer pageNum = questionQueryDTO.getPageNum();
        Integer pageSize = questionQueryDTO.getPageSize();
        PageHelper.startPage(pageNum, pageSize);
        List<QuestionVo> questionVoList = questionMapper.listQuestion(questionQueryDTO);
        return TableDataInfo.success(questionVoList);
    }

    @Override
    public int addQuestion(QuestionAddDTO questionAddDto) {
        // 1. 进行简单的参数校验,都不能为空或为负数
        checkQuestionArgs(questionAddDto);
        // 2. 检查题目是否已经存在(此处名称和内容完全一致则认为重复)
        if (checkQuestionIsRepeat(questionAddDto)) {
            throw new BusinessException("新增失败,新增题目的标题和描述不能和已存在题目完全一致");
        }

        // 3. 将数据插入 数据库,ES,redis 中
        //   最先往数据库添加数据,原因是需要先获取 questionId
        Question question = new Question();
        BeanUtils.copyProperties(questionAddDto, question);
        int insert = questionMapper.insert(question);
        if (insert < 1) {
            throw new BusinessException("题目新增失败");
        }
        QuestionES questionES = new QuestionES();
        BeanUtils.copyProperties(question, questionES);
        questionRepository.save(questionES);
        questionCacheManager.addCache(question.getQuestionId());
        return insert;
    }

    @Override
    @Transactional
    public int deleteQuestion(Long questionId) {
        // 1. 先查看题目是否存在
        Question question = questionMapper.selectById(questionId);
        if (question == null) {
            throw new BusinessException(ResultCode.FAILED_NOT_EXISTS, "题目不存在,删除失败");
        }
        // 2. 在已开始/结束且已发布的竞赛中包含该题目不可被删除
        List<Long> contestIdList = contestQuestionMapper.selectList(new LambdaQueryWrapper<ContestQuestion>()
                        .select(ContestQuestion::getContestId)
                        .eq(ContestQuestion::getQuestionId, questionId))
                        .stream().map(ContestQuestion::getContestId).toList();
        if (!CollectionUtils.isEmpty(contestIdList)) {
            // 查看这些竞赛是否包含已开始或结束的竞赛
            Long count = contestMapper.selectCount(new LambdaQueryWrapper<Contest>()
                    // 开始时间 <= 当前时间
                    .le(Contest::getStartTime, LocalDateTime.now())
                    // 已发布的竞赛
                    .eq(Contest::getStatus, Constants.TRUE)
                    .in(Contest::getContestId, contestIdList));
            if (count > 0) {
                throw new BusinessException("当前题目包含在已开始/结束的竞赛中,无法删除");
            }
        }
        // 3. 删除 竞赛-题目 关系记录信息
        contestQuestionMapper.delete(new LambdaQueryWrapper<ContestQuestion>()
                .eq(ContestQuestion::getQuestionId, questionId));
        // 删除题目记录(包括 数据库,ES和redis 中的记录)
        questionRepository.deleteById(questionId);
        questionCacheManager.deleteCache(questionId);
        int res = questionMapper.deleteById(questionId);
        if (res != 1) {
            throw new BusinessException("题目删除出错,请重新尝试");
        }
        return res;
    }

    @Override
    public int editQuestion(QuestionEditDTO questionEditDto) {
        Long questionId = questionEditDto.getQuestionId();
        // 1. 先查看题目是否存在
        Question question = questionMapper.selectById(questionId);
        if (question == null) {
            throw new BusinessException(ResultCode.FAILED_NOT_EXISTS, "题目不存在,更新失败");
        }
        // 2. 检查更新后的参数是否合法
        checkQuestionArgs(questionEditDto);
        // 3. 如果题目或者描述存在改动,则更新后不能与数据库中原有题目重复
        if (!Objects.equals(question.getTitle(), questionEditDto.getTitle())
            || !Objects.equals(question.getContent(), questionEditDto.getContent())) {
            if (checkQuestionIsRepeat(questionEditDto)) {
                throw new BusinessException("更新失败,编辑后的题目名称和描述不能和已存在题目完全一致");
            }
        }
        // 4. 更新数据库和 ES 数据
        BeanUtils.copyProperties(questionEditDto, question);
        int res = questionMapper.updateById(question);
        if (res != 1) {
            throw new BusinessException("题目更新失败,请重新尝试");
        }
        QuestionES questionES = new QuestionES();
        BeanUtils.copyProperties(question, questionES);
        questionRepository.save(questionES);
        return res;
    }


    @Override
    public QuestionDetailVo getQuestionDetail(Long questionId) {
        Question question = questionMapper.selectById(questionId);
        if (question == null) {
            throw new BusinessException(ResultCode.FAILED_NOT_EXISTS, "题目不存在");
        }
        QuestionDetailVo questionDetailVo = new QuestionDetailVo();
        BeanUtils.copyProperties(question, questionDetailVo);
        return questionDetailVo;
    }


    private void checkQuestionArgs(QuestionAddDTO questionAddDto) {
        String title = questionAddDto.getTitle();
        Integer difficulty = questionAddDto.getDifficulty();
        String content = questionAddDto.getContent();
        Integer timeLimit = questionAddDto.getTimeLimit();
        Integer spaceLimit = questionAddDto.getSpaceLimit();
        String questionCase = questionAddDto.getQuestionCase();
        String defaultCode = questionAddDto.getDefaultCode();
        String mainFunc = questionAddDto.getMainFunc();
        if (StringUtils.isAnyBlank(title, content, questionCase, defaultCode, mainFunc)) {
            throw new BusinessException("输入内容不能为空");
        }
        if (difficulty == null || difficulty < 1 || difficulty > 3) {
            throw new BusinessException("难度选择非法");
        }
        if (timeLimit == null || timeLimit < 1) {
            throw new BusinessException("时间限制输入非法");
        }
        if (spaceLimit == null || spaceLimit < 1) {
            throw new BusinessException("空间限制输入非法");
        }
    }

    private boolean checkQuestionIsRepeat(QuestionAddDTO questionAddDto) {
        String title = questionAddDto.getTitle();
        String content = questionAddDto.getContent();
        Long count = questionMapper.selectCount(new LambdaQueryWrapper<Question>()
                .eq(Question::getTitle, title)
                .eq(Question::getContent, content));
        return count > 0;
    }
}




